﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Description;
using Cubicle.Core.Config;
using Cubicle.Core.DataContracts;
using Cubicle.Core.Interfaces;
using Cubicle.Core.Shared;

namespace Cubicle.Core.Hosting
{
    public class ComponentHost<T> : IComponentHost
    {
        // NLog
        private static NLog.Logger _logger = NLog.LogManager.GetCurrentClassLogger();

        // Binding defaults
        private static readonly TimeSpan ReceiveTimeout = new TimeSpan(0, 0, 30);
        private static readonly TimeSpan SendTimeout = new TimeSpan(0, 0, 30);
        private const int MaxBufferSize = 1024 * 1024 * 10;             // 10Mb
        private const int MaxReceivedMessageSize = 1024 * 1024 * 10;    // 10Mb
        private const int MaxArrayLength = 1024 * 1024 * 10;            // 10Mb

        protected ServiceHost Host { get; set; }

        private readonly T _singleton;

        private readonly string _hostName;

        public ComponentHost(T singleton, EngineConfig config)
        {
            _hostName = config.HostName;
            _singleton = singleton;
        }

        public string StartHost(HostingType type, bool enableMetaData, int port = 20101)
        {
            // Check if the host is already running
            if (Host != null)
            {
                var message = "A Host has already been started. Close current host before restarting.";
                _logger.Error(message);
                return message;
            }

            // Create host
            switch (type)
            {
                case HostingType.Http:
                    Host = CreateHttpServiceHost(enableMetaData, port);
                    break;
                case HostingType.Pipe:
                    Host = CreatePipeServiceHost(enableMetaData);
                    break;
                case HostingType.Tcp:
                    Host = CreateTcpServiceHost(enableMetaData, port);
                    break;
            }

            // Start the host
            try
            {
                Host.Open();
                return GetHostAddress();
            }
            catch (Exception e)
            {
                _logger.ErrorException(e, "Error opening service host. Address = {0}", GetHostAddress());
                return "Error";
            }
        }

        public bool StopHost()
        {
            if (Host == null) return true;
            try
            {
                Host.Close();
                Host = null;
                return true;
            }
            catch (Exception e)
            {
                _logger.ErrorException(e, "Error closing Exocortex host.");
                return false;
            }
        }

        public bool IsHosted() { return Host != null; }

        public string GetHostAddress()
        {
            return IsHosted() ? Host.BaseAddresses.First().ToString() : "";
        }

        private ServiceHost CreatePipeServiceHost(bool enableMetaData)
        {
            var server = "localhost";

            // Set up binding
            var pipeBinding = new NetNamedPipeBinding(NetNamedPipeSecurityMode.None);
            SetPipeBindingDefaults(pipeBinding);

            // Set up URI
            var pipeUri = new Uri("net.pipe://" + server + "/" + _hostName);

            // Set up service host
            var sh = new ServiceHost(_singleton, new[] { pipeUri });

            // Bind end-points to specific protocols
            sh.AddServiceEndpoint(typeof(IReflectionEngine), pipeBinding, pipeUri);
            _logger.Info("Creating service on URI = " + pipeUri);

            // Add metadata
            if (enableMetaData)
            {
                var smb = new ServiceMetadataBehavior();
                sh.Description.Behaviors.Add(smb);
                sh.AddServiceEndpoint(
                    typeof(IMetadataExchange),
                    MetadataExchangeBindings.CreateMexNamedPipeBinding(),
                    pipeUri.ToString() + "/mex");
            }

            // Return service host
            return sh;
        }

        private ServiceHost CreateHttpServiceHost(bool enableMetaData, int port)
        {
            var server = "localhost";
            var securityMode = SecurityMode.None;

            // Set up binding
            var binding = new WSHttpBinding(securityMode);
            SetHttpBindingDefaults(binding);

            // Set up URI
            var uriBuilder = new UriBuilder(
                Uri.UriSchemeHttp, server, port, _hostName);
            var uris = new List<Uri> { uriBuilder.Uri };

            // Set up service host
            var sh = new ServiceHost(_singleton, uris.ToArray());

            // Bind end-points to specific protocols
            for (int i = 0; i < uris.Count; i++)
                sh.AddServiceEndpoint(typeof(IReflectionEngine), binding, uris[i]);
            _logger.Info("Creating service on URI = {0}", uris[0]);

            // Add metadata
            if (enableMetaData)
            {
                var smb = new ServiceMetadataBehavior
                    {
                        MetadataExporter = { PolicyVersion = PolicyVersion.Policy15 },
                        HttpGetEnabled = true
                    };

                sh.Description.Behaviors.Add(smb);
                sh.AddServiceEndpoint(typeof(IMetadataExchange), binding, "MEX");
            }

            // Return service host
            return sh;
        }

        private ServiceHost CreateTcpServiceHost(bool enableMetaData, int port)
        {
            var server = "localhost";
            var securityMode = SecurityMode.None;

            // Set up binding
            var binding = new NetTcpBinding(securityMode);
            SetTcpBindingDefaults(binding);

            // Set up URI
            var uriBuilder = new UriBuilder(
                Uri.UriSchemeNetTcp, server, port, _hostName);
            var uris = new List<Uri> { uriBuilder.Uri };

            // Set up service host
            var sh = new ServiceHost(_singleton, uris.ToArray());

            // Bind end-points to specific protocols
            for (int i = 0; i < uris.Count; i++)
                sh.AddServiceEndpoint(typeof(IReflectionEngine), binding, uris[i]);
            _logger.Info("Creating service on URI = {0}", uris[0]);

            // Add metadata
            if (enableMetaData)
            {
                var smb = new ServiceMetadataBehavior
                    {
                        MetadataExporter = { PolicyVersion = PolicyVersion.Policy15 }
                    };

                sh.Description.Behaviors.Add(smb);
                sh.AddServiceEndpoint(typeof(IMetadataExchange), binding, "MEX");
            }

            // Return service host
            return sh;
        }


        private static void SetHttpBindingDefaults(WSHttpBinding b)
        {
            b.ReceiveTimeout = ReceiveTimeout;
            b.SendTimeout = SendTimeout;
            b.MaxReceivedMessageSize = MaxReceivedMessageSize;
            b.ReaderQuotas.MaxArrayLength = MaxArrayLength;
            b.ReaderQuotas.MaxStringContentLength = MaxArrayLength;
            b.ReaderQuotas.MaxBytesPerRead = MaxArrayLength;
        }

        private static void SetTcpBindingDefaults(NetTcpBinding b)
        {
            b.ReceiveTimeout = ReceiveTimeout;
            b.SendTimeout = SendTimeout;
            b.MaxReceivedMessageSize = MaxReceivedMessageSize;
            b.ReaderQuotas.MaxArrayLength = MaxArrayLength;
            b.ReaderQuotas.MaxStringContentLength = MaxArrayLength;
            b.ReaderQuotas.MaxBytesPerRead = MaxArrayLength;
        }

        private static void SetPipeBindingDefaults(NetNamedPipeBinding b)
        {
            b.ReceiveTimeout = ReceiveTimeout;
            b.SendTimeout = SendTimeout;
            b.MaxReceivedMessageSize = MaxReceivedMessageSize;
            b.MaxBufferSize = MaxBufferSize;
            b.ReaderQuotas.MaxArrayLength = MaxArrayLength;
            b.ReaderQuotas.MaxStringContentLength = MaxArrayLength;
            b.ReaderQuotas.MaxBytesPerRead = MaxArrayLength;
        }

        #region Implementation of IDisposable

        public void Dispose()
        {
            if (Host == null) return;
            try
            {
                Host.Close();
            }
            catch (Exception e)
            {
                _logger.ErrorException(e, "Error closing Exocortex host.");
            }
        }

        #endregion

    }
}